#define EXMALLOC_IMPL

#include "cdsl.h"

#include <malloc.h>

#ifdef MEMDIAG

# define MEM_INCREASE_RATE (100)
# define MEM_CHART_HIGHT   (25)
# define MEM_CHART_WIDE    (100)

#ifdef log_error
# undef log_error
#endif

static void log_error(char *format, ...)
{
    va_list va;

    va_start(va, format);
    vfprintf(stderr, format, va);
    va_end(va);
    fflush(stderr);
}

static void mem_sample()
{
    if ((global_mem.frees+global_mem.reallocs+global_mem.mallocs) % global_mem.sampleTime == 0) {
        if (global_mem.samples.count >= global_mem.samples.size) {
            global_mem.samples.values = (typeof(global_mem.samples.values)) realloc(global_mem.samples.values, sizeof(typeof(*global_mem.samples.values))*(global_mem.samples.size+MEM_INCREASE_RATE));
            global_mem.samples.size += MEM_INCREASE_RATE;
        }
        global_mem.samples.values[global_mem.samples.count++] = global_mem.current;
    }
}

void mem_diag_dump()                                           
{                                                                     
    struct mem_obj_info *__tmp__, *__aux__;                           
    int index1, index2, current, group, peak, max;
    double scale;

    log_error("\n--------------------------------------------\n", ""); 
    log_error("| You have %d leaks:\n|\n", vect_size(&global_mem.obj_list)); 
    vect_front(&global_mem.obj_list, __tmp__, list);                  
    while(!vect_empty(&global_mem.obj_list)) {                        
        log_error("|    LEAK: %p, %s:%d\n", __tmp__->addr, __tmp__->file, __tmp__->line); 
        __aux__=__tmp__;                                              
        vect_delete(&global_mem.obj_list, __tmp__, list);             
        free(__aux__);                                                
    }                                                                 
    log_error("|\n--------------------------------------------\n\n"); 
    log_error("\n--------------------------------------------\n"); 
    log_error("| Total number of mallocs:   %d\n", global_mem.mallocs); 
    log_error("| Total number of reallocs:  %d\n", global_mem.reallocs);
    log_error("| Total number of frees:     %d\n", global_mem.frees); 
    log_error("| Total bytes leaked:        %d\n", global_mem.current); 
    log_error("| Memory peak (KB):          %d\n", kb(global_mem.peak)); 
    log_error("--------------------------------------------\n"); 

    current = index2 = peak = max = 0;
    group = global_mem.samples.count/MEM_CHART_WIDE;
    for (index1=0; index1<global_mem.samples.count; index1++) {
        if (peak < global_mem.samples.values[index1]) {
            peak = global_mem.samples.values[index1];
            if (max < peak) {
                max = peak;
            }
        }
        index2 ++;
        if (index2 >= group) {
            global_mem.samples.values[current++] = peak;
            index2 = peak = 0;
        }
    }
    if (index2 > 0) {
        global_mem.samples.values[current++] = peak;
    }
    global_mem.samples.count = current;
    scale = (double)max/MEM_CHART_HIGHT;
    for (index1=0; index1<global_mem.samples.count; index1++) {
        global_mem.samples.values[index1] = (int)(global_mem.samples.values[index1]/scale);
    }
    log_error("\n******************************************** Starting the chart ******************************************\n");
    for (index2=MEM_CHART_HIGHT; index2>0; index2--) {
        log_error("|  ");
        for (index1=0; index1<global_mem.samples.count; index1++) {
            if (global_mem.samples.values[index1] > index2) {
                log_error("|");
            }
            else if (global_mem.samples.values[index1] == index2) {
                log_error("*");
            }
            else {
                log_error(" ");
            }
        }
        log_error("  |\n");
    }
    log_error("*************************************************** Done *************************************************\n");
    free(global_mem.samples.values);
}                                                                     

#endif

#ifdef MEMDIAG
void exfree(void *addr, const char *file, const int line)
#else
void exfree(void *addr)
#endif /* MEMDIAG */
{
    if (addr != NULL) {
        free(addr);
    }
#ifdef MEMDIAG
    {
     struct mem_obj_info *info, *aux;

        for(info=vect_begin(&global_mem.obj_list, struct mem_obj_info, list);
            info;
            info=vect_next(&global_mem.obj_list, info, list)) {
            if (info->addr == addr) {
                aux = info;
                global_mem.current -= info->size;
                vect_delete(&global_mem.obj_list, info, list);
                free(aux);
                global_mem.frees ++;
                break;
            }
        }
        mem_sample();
    }
#endif /* MEMDIAG */
}

#ifdef MEMDIAG
void *exmalloc(int size, const char *file, const int line)
#else
void *exmalloc(int size)
#endif /* MEMDIAG */
{
 void *tmp = NULL;

    exassert(size >= 0 && "Can't allocate a negative number of bytes");

    if ((tmp = malloc(size)) == NULL) {
        throw(mem_alloc_failed);
    }

#ifdef MEMDIAG
    {
     struct mem_obj_info *info;

        if ((info = (typeof(info)) malloc(sizeof(typeof(*info)))) == NULL) {
            throw(mem_alloc_failed);
        }
        info->addr = tmp;
        strncpy(info->file, file, array_size(info->file));
        info->line = line;
        info->size = size;
        vect_push_front(&global_mem.obj_list, &info->list);
        global_mem.mallocs ++;
        global_mem.current += size;
        if (global_mem.peak < global_mem.current) {
            global_mem.peak = global_mem.current;
        }
        mem_sample();
    }
#endif /* MEMDIAG */

    return tmp;
}

#ifdef MEMDIAG
void *exrealloc(void *addr, int size, const char* file, const int line)
#else
void *exrealloc(void *addr, int size)
#endif /* MEMDIAG */
{
 void *tmp = NULL;

    exassert(size >= 0 && "Can't allocate a negative number of bytes");

    if ((tmp = realloc(addr, size)) == NULL) {
        throw(mem_alloc_failed);
    }

#ifdef MEMDIAG
    {
     struct mem_obj_info *info;

        for(info=vect_begin(&global_mem.obj_list, struct mem_obj_info, list);
            info;
            info=vect_next(&global_mem.obj_list, info, list)) {
            if (info->addr == addr) {
                global_mem.current -= info->size;
                break;
            }
        }
        if (!info) {
            if ((info = (typeof(info)) malloc(sizeof(typeof(*info)))) == NULL) {
                throw(mem_alloc_failed);
            }
            vect_push_front(&global_mem.obj_list, &info->list);
        }
        global_mem.reallocs ++;
        global_mem.current += size;
        if (global_mem.peak < global_mem.current) {
            global_mem.peak = global_mem.current;
        }
        info->addr = tmp;
        strncpy(info->file, file, array_size(info->file));
        info->line = line;
        info->size = size;
        mem_sample();
    }
#endif /* MEMDIAG */

    return tmp;
}
